/**
  ******************************************************************************
  * @file           
  * @author         古么宁
  * @brief          IEC 104 文件传输相关部分
  * @note
  * 召唤目录 
  * <pre>
  *  master                           slave
  *    | ----TI=210,COT=5,operate=1---> | 主站召唤目录
  *    | <---TI=210,COT=5,operate=2---- | 从站召唤目录确认
  * </pre>
  * 
  * 读文件传输过程
  * <pre>
  *  master                           slave
  *    | ----TI=210,COT=6,operate=3---> | 主站读文件激活
  *    | <---TI=210,COT=7,operate=4---- | 从站读文件激活确认
  *    | <---TI=210,COT=5,operate=5---- | 从站读文件数据传输
  *    | <---         ...          ---- | 从站读文件数据传输
  *    | ----TI=210,COT=5,operate=6---> | 主站读文件数据确认
  *    | <---TI=210,COT=5,operate=5---- | 从站读文件数据传输
  *    | <---         ...          ---- | 从站读文件数据传输
  *    | ----TI=210,COT=5,operate=6---> | 主站读文件数据确认
  * </pre>
  * 
  * 写文件过程
  * <pre>
  *  master                           slave
  *    | ----TI=210,COT=6,operate=7---> | 主站写文件激活
  *    | <---TI=210,COT=7,operate=8---- | 从站写文件激活确认
  *    | ----TI=210,COT=5,operate=9---> | 主站写文件数据传输
  *    | ----         ...          ---> | 主站写文件数据传输
  *    | <---TI=210,COT=5,operate=10--- | 从站写文件激活确认
  *    | ----TI=210,COT=5,operate=9---> | 主站写文件数据传输
  *    | ----         ...          ---> | 主站写文件数据传输
  *    | <---TI=210,COT=5,operate=10--- | 从站写文件激活确认
  * <pre>
  ******************************************************************************
  *
  * COPYRIGHT(c) 2019 GoodMorning
  *
  ******************************************************************************
  */
/* Includes ---------------------------------------------------*/
#include <string.h>

#include "iec10x.h"
#include "iec10x_link.h"
#include "iec10x_telesgnl.h"
#include "iec10x_event.h"
#include "iec10x_debug.h"
#include "iec10x_fs.h"

#include "COT.h"

#if (ENABLE_FILE_TRANS)
#include "iec_file.h"

/* Private types ------------------------------------------------------------*/

/// 从机发送文件至主站过程参数
struct fsend_arg {
	char  *         file    ;
	iec_file_t *    iecfile ;
	int             fsize   ;
	int             ftell   ;
	int             confirm ;
};


struct file_cmd {
	char  *         file    ;
	int             namelen ;
	int             fsize   ;
	int             fID     ;
	int             mode    ; ///< 'r' or 'w'
};

/* Private macro ------------------------------------------------------------*/


#define ACK_PACKET ACK_SLIDING_WINDOW

/* Private variables --------------------------------------------------------*/

/* Global  variables --------------------------------------------------------*/

/* Private function prototypes ----------------------------------------------*/

/* Gorgeous Split-line ------------------------------------------------------*/


#if (ENABLE_DEBUG_INFO > 0)
void fnode_info(iec10x_t *bus)
{
	struct iec_slist  * node = bus->filelist.next;
	struct file_node * fnode ;

	while (node != NULL) {
		fnode = container_of(node, struct file_node,node);
		node  = node->next;
		iec10x_msg(bus,"%-20s(%02d/%02d/%02d %02d:%02d) size:%d\r\n",
			fnode->name,
			fnode->ftime.year,fnode->ftime.month,fnode->ftime.day.date,
			fnode->ftime.hour,fnode->ftime.min,
			fnode->fsize);
	}
}
#else
#define fnode_info(x) 
#endif

/**
  * @brief    比较两个 CP56Time2a 类型的时间戳
  * @param    _time1  : 比较时间戳1
  * @param    _time2  : 比较时间戳2
  * @return   比较值
  *   @retval 0  : _time1 == _time2
  *   @retval 1  : _time1 >  _time2
  *   @retval -1 : _time1 <  _time2
*/
int time_cmp(struct CP56Time2a * _time1 , struct CP56Time2a * _time2)
{
	signed char * time1 =(signed char *)&_time1->year ;
	signed char * time2 =(signed char *)&_time2->year ;

	for (int i = 0 ; i < sizeof(time1) ; i++) {
		if (*time1 < *time2) {
			return -1 ;
		}
		else
		if (*time1 > *time2) {
			return 1 ;
		}
		else {
			time1--;
			time2--;
		}
	}

	return 0;
}




/**
  * @brief    加载文件夹所有的内容
  * @param    bus     : 正在读取文件夹的总线
  * @param    dirname : 文件夹名称
  * @param    tfilter : 是否根据时间戳进行文件过滤
  * @param    min     : 查询起始时间
  * @param    max     : 查询终止时间
  * @return   成功返回 0 ，失败返回 -1 static
*/
 int iec104slv_dir_read(iec10x_t * bus ,const char * dirname ,int tfilter,
                           struct CP56Time2a * min , struct CP56Time2a * max)
{
	iec_dir_t * dir ;
	struct CP56Time2a ftime ;
	char file_name[256] = {0};
	int  file_size = 0 , namelen;
	struct file_node * fnode ;
	int pktlen = 0;

	dir = iec_opendir(dirname);
	if (dir == NULL) {
		iec10x_warn(bus,"cannot read directory:%s\r\n",dirname);
		return -1;
	}

	do {
		/* 从文件夹读取内容 */
		file_size = iec_readdir(dir,file_name,&ftime) ;
		if (file_size < 0 ) {
			break ;
		}

		namelen = strlen(file_name) ;
		if (namelen > 160) {
			iec10x_warn(bus,"file name too long:%s\r\n",file_name);
			continue ;
		}

		if (tfilter) {
			/* 小于查询起始时间或大于查询终止时间的文件不加载 */
			if (time_cmp(&ftime,min) < 0 || time_cmp(max,&ftime) < 0) {
				continue ;
			}
		}

		/* 申请内存记录当前的文件信息 */
		pktlen = namelen + sizeof(struct file_node);
		fnode = iec_malloc(pktlen);
		if (NULL == fnode) {
			iec10x_warn(bus,"cannot malloc buffer.\r\n");
			break ;
		}

		memset(fnode,0,pktlen);

		/* 记录信息，头插法加入链表 */
		memcpy(fnode->name,file_name,namelen);
		memcpy(&fnode->ftime,&ftime,sizeof(fnode->ftime));
		fnode->fsize       = file_size ;
		fnode->namelen     = namelen;
		fnode->node.next   = bus->filelist.next ;
		bus->filelist.next = &fnode->node ;
	}while (file_size > -1);
	
	/* 关闭文件夹 */
	iec_closedir(dir);

	fnode_info(bus);

	return 0;
}


static void iec104slv_dir_close(iec10x_t * bus)
{
	struct file_node *  fnode ;
	struct iec_slist  * node = bus->filelist.next;
	while (node) {
		fnode = container_of(node, struct file_node,node);
		node = node->next;
		iec_free(fnode);
	}
	
	bus->busy     &= ~IEC10x_FILE ;
	bus->interrupt = NULL;
	memset(&bus->fs, 0, sizeof(struct iec_do));
}




/**
  * @author   古么宁
  * @brief    从机遥控响应发送函数
  * @param    bus      : 总线
  * @return   don't carestatic
*/
 void iec104slv_dir_confirm(iec10x_t * bus)
{
	IEC_INDEX(bus->causelen,bus->cmnaddrlen);
	unsigned char * ASDU = ASDU_TXBUFF(bus);
	unsigned char * info = &ASDU[IEC_INFO];
	int remain ;
	struct dir_confirm_pkt  * dirpkt ;
	struct iec_slist  ** node = &bus->filelist.next;
	struct file_node * fnode ;

	ASDU[IEC_TYPE]          = 0xD2                    ;
	ASDU[IEC_QUALIFIER]     = 0x01                    ; 
	ASDU[IEC_COT]           = COT_REQUEST             ;
	ASDU[IEC_COT+1]         = bus->connectaddr & 0xff ;
	ASDU[IEC_COMMON_ADDR]   = bus->cmnaddr     & 0xff ;
	ASDU[IEC_COMMON_ADDR+1] = bus->cmnaddr     >> 8   ;
	ASDU[IEC_INFO]          = 0                       ;
	ASDU[IEC_INFO+1]        = 0                       ;
	ASDU[IEC_INFO+2]        = 0                       ;
	
	for (int i = 0 ; i < bus->infoaddrlen ; ++i) {
		*info++ = 0;
	}

	/* 填充目录召唤确认信息内容，操作标识为 DIR_READ_ACK */
	dirpkt = (struct dir_confirm_pkt *)info;
	memset(dirpkt, 0, sizeof(struct dir_confirm_pkt));
	dirpkt->type    = 2            ;
	dirpkt->operate = DIR_READ_ACK ;

	/* 剩余可用存放文件描述数据包的内存大小 */
	info = dirpkt->data;
	remain = bus->pktmax - (info - ASDU) ;

	while (*node != NULL) {
		fnode = container_of(*node, struct file_node,node);

		if (remain > sizeof(struct dir_file_list) + fnode->namelen) {
			/* 有足够的空间容纳当前文件描述数据包 */
			/* 依次填充文件名长度，文件名，属性，大小，时间 */
			*info++ = fnode->namelen;

			for (int i = 0; i < fnode->namelen ; ++i) {
				*info++ = fnode->name[i];
			}

			*info++ = 0 ; // 文件属性
			*info++ = (fnode->fsize >> 0 ) & 0xff ;
			*info++ = (fnode->fsize >> 8 ) & 0xff ;
			*info++ = (fnode->fsize >> 16) & 0xff ;
			*info++ = (fnode->fsize >> 24) & 0xff ;
			memcpy(info,&fnode->ftime,sizeof(struct CP56Time2a));
			info += sizeof(struct CP56Time2a);

			/* 释放当前文件内存 */
			*node = (*node)->next ;
			iec_free(fnode);

			/* 更新当前 ASDU 包总文件数和剩余内存大小 */
			dirpkt->files++;
			remain = bus->pktmax - (info - ASDU) ;
			if (remain <= sizeof(struct dir_file_list)) {
				break;
			}
		}
		else {
			node = &((*node)->next);
		}
	}

	if (bus->filelist.next) {
		/* 仍存在未发送的文件描述数据包，即有后续 */
		dirpkt->continuation = 1;
	}
	else {
		iec104slv_dir_close(bus);
	}

	iec104_protocol_send(bus,ASDU,info-ASDU,0);
}


/**
  * @author   古么宁
  * @brief    从机响应读取文件夹操作
  * @param    bus      : 总线
  * @param    ASDUrx   : 接收到的 ASDU 数据包
  * @param    ASDUlen  : 接收到的 ASDU 数据包长度
  * @return   don't care
*/
static void iec104slv_dir_open(iec10x_t * bus , uint8_t * ASDU,int32_t len)
{
	IEC_INDEX(bus->causelen,bus->cmnaddrlen);
	struct dir_get * data;
	int tfilter = 0;
	struct CP56Time2a * min ;
	struct CP56Time2a * max ;

	data = (struct dir_get *)&ASDU[IEC_INFO + bus->infoaddrlen];

	if (data->type != 2 || data->operate != 1) {
		return ;
	}

	if (data->dirlen > sizeof(bus->workdir)) {
		return ;
	}

	/* 获取时间过滤标志，为 1 时搜索满足时间段的文件 */
	tfilter = data->dirname[data->dirlen];

	/* 添加结束符 */
	data->dirname[data->dirlen] = '\0';
	min = (struct CP56Time2a *)&data->dirname[data->dirlen + 1] ;
	max = min + 1;

	/* 记录当前的工作目录 */
	memcpy(bus->workdir,data->dirname,data->dirlen);
	bus->workdir[data->dirlen] = '\0';

	if (iec104slv_dir_read(bus,bus->workdir,tfilter,min,max) < 0) {
		iec10x_warn(bus,"cannot open directory:%s\r\n",bus->workdir);
	}
	else {
		iec10x_msg(bus,"request directory:%s\r\n",bus->workdir);
	}

	bus->busy     |= IEC10x_FILE               ;
	bus->fs.todo   = iec104slv_dir_confirm     ;
	bus->interrupt = iec104slv_dir_close       ;
}



/**
  * @author   古么宁
  * @brief    从机发送文件过程中断处理
  * @param    bus      : 总线
  * @note     如链路中断等，中断后需要及时释放系统资源
  * @return   don't care
*/
static void iec104slv_fclose(iec10x_t * bus)
{
	/* 文件发送流程中间参数，存于 bus->fs.argv */
	struct fsend_arg * tmp = (struct fsend_arg *)bus->fs.argv;
	if (tmp->file) {
		iec_free(tmp->file) ;
		tmp->file = NULL;
	}

	if (tmp->iecfile) {
		iec_fclose(tmp->iecfile);
		tmp->iecfile = NULL;
	}

	bus->busy     &= ~IEC10x_FILE ;
	bus->interrupt = NULL;
	memset(&bus->fs,0,sizeof(struct iec_do));
}





/**
  * @author   古么宁
  * @brief    从机接收文件数据回复确认报文
  * @param    bus      : 总线
  * @param    ASDUrx   : 接收到的 ASDU 数据包
  * @param    ASDUlen  : 接收到的 ASDU 数据包长度
  * @return   don't care
*/
static void iec104slv_frecv_ack(iec10x_t * bus)
{
	/* 文件发送流程中间参数，存于 bus->fs.argv */
	struct fsend_arg * tmp = (struct fsend_arg *)bus->fs.argv;
	unsigned char * ASDU = ASDU_TXBUFF(bus);
	unsigned char * end ;
	struct read_ack_pkt * pkt;
	IEC_INDEX(bus->causelen,bus->cmnaddrlen);
	
	ASDU[IEC_TYPE]          = 0xD2                    ;
	ASDU[IEC_QUALIFIER]     = 0x01                    ; 
	ASDU[IEC_COT]           = COT_REQUEST             ;
	ASDU[IEC_COT+1]         = bus->connectaddr & 0xff ;
	ASDU[IEC_COMMON_ADDR]   = bus->cmnaddr     & 0xff ;
	ASDU[IEC_COMMON_ADDR+1] = bus->cmnaddr     >> 8   ;
	ASDU[IEC_INFO]          = 0                       ;
	ASDU[IEC_INFO+1]        = 0                       ;
	ASDU[IEC_INFO+2]        = 0                       ;

	pkt = (struct read_ack_pkt *)&ASDU[IEC_INFO + bus->infoaddrlen];
	pkt->type    = 2                          ;
	pkt->option  = FILE_WR_DATA_ACK           ;
	pkt->ID[0]   = 0                          ;
	pkt->ID[1]   = 0                          ;
	pkt->ID[2]   = 0                          ;
	pkt->ID[3]   = 0                          ;
	pkt->ftell[0] = (tmp->ftell >> 0 ) & 0xff ;
	pkt->ftell[1] = (tmp->ftell >> 8 ) & 0xff ;
	pkt->ftell[2] = (tmp->ftell >> 16) & 0xff ;
	pkt->ftell[3] = (tmp->ftell >> 24) & 0xff ;

	end = (unsigned char *)&pkt->continuation;
	*end++ = tmp->confirm          ;

	iec104_protocol_send(bus,ASDU,end-ASDU,0);
	bus->fs.todo = NULL;
}

/**
  * @author   古么宁
  * @brief    从机接收主站的文件数据，写入文件
  * @param    bus      : 总线
  * @param    ASDUrx   : 接收到的 ASDU 数据包
  * @param    ASDUlen  : 接收到的 ASDU 数据包长度
  * @return   don't care
*/
static void fiec104slv_fwrite(iec10x_t * bus, uint8_t * ASDU,int32_t ASDUlen)
{
	/* 文件发送流程中间参数，存于 bus->fs.argv */
	struct fsend_arg * tmp = (struct fsend_arg *)bus->fs.argv;
	struct file_data_pkt * pkt;
	unsigned char * filedata;
	int fsize = 0, continuation = 0;
	int ftell = 0;
	unsigned char checksum = 0;
	IEC_INDEX(bus->causelen,bus->cmnaddrlen);

	pkt = (struct file_data_pkt *)&ASDU[IEC_INFO + bus->infoaddrlen];
	continuation = pkt->continuation;
	filedata = (unsigned char *)pkt->data;

	/* 当前数据包可用的文件数据大小 */
	fsize = ASDUlen - 1 - (filedata - ASDU);

	tmp->confirm = 1; // 假设传输错误

	/* 监测段号，和校验是否一致，不一致回复错误信息报文 */
	ftell |= (pkt->ftell[0])       ;
	ftell |= (pkt->ftell[1] << 8)  ;
	ftell |= (pkt->ftell[2] << 16) ;
	ftell |= (pkt->ftell[3] << 24) ;
	if (ftell != tmp->ftell) {
		iec10x_warn(bus,"slv recv ftell error.\r\n");
		iec104slv_fclose(bus);
		bus->fs.todo = iec104slv_frecv_ack;
		return ;
	}
	else {
		tmp->ftell += fsize ;
	}

	for (int i = 0 ; i < fsize ; i++ , filedata++) {
		checksum += *filedata ;
	}

	if (checksum != *filedata) {
		iec10x_warn(bus,"file packet checksum error!\r\n");
		iec104slv_fclose(bus);
		tmp->confirm = 1;
		bus->fs.todo = iec104slv_frecv_ack;
		return ;
	}

	/* 把当前数据包写入文件 */
	if (tmp->iecfile) {
		if (fsize != iec_fwrite(tmp->iecfile,pkt->data,fsize)) {
			iec104slv_fclose(bus);
			iec10x_warn(bus,"fwrite error!\r\n");
		}
		else {
			tmp->confirm = 0;
		}
	}

	if (0 == continuation) {
		/* 无后续即传输结束 */
		if (tmp->ftell != tmp->fsize) {
			iec104slv_fclose(bus);
			tmp->confirm = 1 ; ///< 回复错误
			iec10x_warn(bus,"file size error.\r\n");
		}
		else {
			iec10x_msg(bus,"get file:%s\r\n", tmp->file);
			iec104slv_fclose(bus);
		}
		
		/* 以上操作将清空 bus->fs.argv 缓存，此处保留 tmp->ftell ，
		   在 iec104slv_frecv_ack 发送需要用到文件段号 */
		tmp->ftell   = ftell + fsize ; 
		bus->fs.todo = iec104slv_frecv_ack;
		return ;
	}

	#if  (ACK_PACKET == ACK_SLIDING_WINDOW)
		
		/* 发送读文件数据传输确认帧，更新滑动窗口值 */
		int recvcheck = (ASDU[-4] >> 1)|(ASDU[-3] << 7);
		if (recvcheck - bus->iec104.rxcheck >= bus->iec104.w - 1) {
			bus->fs.todo   = iec104slv_frecv_ack ;
		}

	#elif (ACK_PACKET == ACK_EACH_PACKET)
		/* 对每帧数据帧都回复数据确认帧 */
		bus->fs.todo   = iec104mst_frecv_ack ;
	#endif
}


/**
  * @author   古么宁
  * @brief    从机发送文件数据至主站端
  * @param    bus      : 总线
  * @param    ASDUrx   : 接收到的 ASDU 数据包
  * @param    ASDUlen  : 接收到的 ASDU 数据包长度
  * @note     此函数交由 bus->fs.todo 调用
  * @return   don't care
*/
static void iec104slv_fsend(iec10x_t * bus)
{
	/* 文件发送流程中间参数，存于 bus->fs.argv */
	struct fsend_arg * tmp = (struct fsend_arg *)bus->fs.argv;
	struct file_data_pkt *fpkt ;
	unsigned char * ASDU = ASDU_TXBUFF(bus);
	unsigned char *filedata,sumchk = 0;
	int readn = 0, continuation = 0;
	int remain = 0, ftell = 0;
	int len , max = bus->pktmax;
	IEC_INDEX(bus->causelen,bus->cmnaddrlen);

	fpkt = (struct file_data_pkt*)&ASDU[IEC_INFO + bus->infoaddrlen];
	filedata = (unsigned char *)&fpkt->data[0];
	remain  = tmp->fsize - tmp->ftell ;
	ftell   = tmp->ftell ;

	/* 先填充基本 ASDU 信息 */
	ASDU[IEC_TYPE]          = 0xD2                    ;
	ASDU[IEC_QUALIFIER]     = 0x01                    ; 
	ASDU[IEC_COT]           = COT_REQUEST             ;
	ASDU[IEC_COT+1]         = bus->connectaddr & 0xff ;
	ASDU[IEC_COMMON_ADDR]   = bus->cmnaddr     & 0xff ;
	ASDU[IEC_COMMON_ADDR+1] = bus->cmnaddr     >> 8   ;
	ASDU[IEC_INFO]          = 0                       ;
	ASDU[IEC_INFO+1]        = 0                       ;
	ASDU[IEC_INFO+2]        = 0                       ;

	if (tmp->fsize) {
		/* 文件大小不为 0 ，计算当前包最大发送数据量，预留一个字节的和校验 */
		max = bus->pktmax - 1 - (filedata - ASDU);
		if (max > remain) {
			max = remain;
		}

		readn = iec_fread(tmp->iecfile,fpkt->data,max);
		if (readn != max) {
			/* 读取错误 */
			readn = 0       ;
			ftell = 0       ;
			continuation = 0;
			iec10x_warn(bus,"read error!\r\n");
		}
	}
	
	tmp->ftell += readn ;
	if (tmp->ftell == tmp->fsize || readn != max) {
		/* 发送完所有数据包 */
		continuation = 0;
		bus->interrupt = NULL;
		iec104slv_fclose(bus);
	}
	else {
		continuation = 1;
	}

	fpkt->type         = 2                    ;
	fpkt->operate      = FILE_RD_DATA         ;
	fpkt->ID[0]        = 0                    ;
	fpkt->ID[1]        = 0                    ;
	fpkt->ID[2]        = 0                    ;
	fpkt->ID[3]        = 0                    ;
	fpkt->ftell[0]     = (ftell >> 0 ) & 0xff ;
	fpkt->ftell[1]     = (ftell >> 8 ) & 0xff ;
	fpkt->ftell[2]     = (ftell >> 16) & 0xff ;
	fpkt->ftell[3]     = (ftell >> 24) & 0xff ;
	fpkt->continuation = continuation ;

	/* 计算和校验 */
	for (int i = 0 ; i < readn ; i++) {
		sumchk += *filedata;
		filedata++;
	}
	*filedata++ = sumchk ;

	len = filedata - ASDU ;
	iec104_protocol_send(bus,ASDU,len,0);
	iec10x_msg(bus,"file bytes:%d,packet length: %d\r\n",readn,len);

	/* 发送达到最大滑动窗口数，等待主站回复数据帧确认 iec104slv_fsend_ack  */
	if (bus->iec104.txnums - bus->iec104.txcheck >= bus->iec104.k) {
		iec10x_msg(bus,"MAX windows sending...\r\n");
		bus->fs.todo = NULL;
	}
}


/**
  * @author   古么宁
  * @brief    从机发送文件过程中，接收到主站的传输确认
  * @param    bus      : 总线
  * @param    ASDUrx   : 接收到的 ASDU 数据包
  * @param    ASDUlen  : 接收到的 ASDU 数据包长度
  * @return   don't care
*/
static void iec104slv_fsend_ack(iec10x_t * bus , uint8_t * ASDU,int len)
{
	IEC_INDEX(bus->causelen,bus->cmnaddrlen);
	struct read_ack_pkt *fpkt ;
	fpkt = (struct read_ack_pkt *)&ASDU[IEC_INFO + bus->infoaddrlen];

	if (!fpkt->continuation) {
		/* 主站回复无后续，传输结束 */
		iec104slv_fclose(bus);
		bus->interrupt = NULL;
	}
	else {
		/* 继续发送数据包 */
		bus->fs.todo = iec104slv_fsend ;
		iec10x_msg(bus,"master confirm.\r\n");
	}
}

/**
  * @author   古么宁
  * @brief    从机接收到文件激活命令，回复激活确认
  * @param    bus      : 总线
  * @note     此函数交由 bus->fs.todo 调用
  * @return   don't care
*/
static void iec104slv_fsend_confirm(iec10x_t * bus)
{
	IEC_INDEX(bus->causelen,bus->cmnaddrlen);
	unsigned char * ASDU = ASDU_TXBUFF(bus);
	unsigned char result = 1;
	char * file = (char *)bus->fs.argv[0];
	int    fnamelen = bus->fs.argv[1];
	int    fsize    = 0 ;
	iec_file_t *iecfile = NULL;
	struct file_confirm_pkt *fpkt;
	unsigned char * info = &ASDU[IEC_INFO + bus->infoaddrlen];

	/* 先填充基本信息 */
	ASDU[IEC_TYPE]          = 0xD2                    ;
	ASDU[IEC_QUALIFIER]     = 0x01                    ; 
	ASDU[IEC_COT]           = COT_ACTIVATE_CONFIRM    ;
	ASDU[IEC_COT+1]         = bus->connectaddr & 0xff ;
	ASDU[IEC_COMMON_ADDR]   = bus->cmnaddr     & 0xff ;
	ASDU[IEC_COMMON_ADDR+1] = bus->cmnaddr     >> 8   ;
	ASDU[IEC_INFO]          = 0                       ;
	ASDU[IEC_INFO+1]        = 0                       ;
	ASDU[IEC_INFO+2]        = 0                       ;

	/* 附加数据包 */
	fpkt = (struct file_confirm_pkt*)info;

	memset(&bus->fs,0,sizeof(struct iec_do));

	if (0 == fnamelen) {
		iec_free(file);
		goto cleanup ;
	}

	/* 获取文件大小，并打开文件，记录文件参数至 argv 缓存区中 */
	fsize = iec_fsize(bus->workdir,file);
	if (fsize < 0) {
		fsize = 0 ;
		goto cleanup;
	}

	iecfile = iec_fopen(bus->workdir,file,'r');
	if (NULL == iecfile) {
		iec_free(file);
		fsize = 0 ;
		goto cleanup;
	}

	struct fsend_arg * tmp = (struct fsend_arg *)bus->fs.argv;
	tmp->file    = file    ;
	tmp->iecfile = iecfile ;
	tmp->fsize   = fsize   ;
	tmp->ftell   = 0       ;
	tmp->confirm = 0       ;
	bus->fs.todo = iec104slv_fsend ;
	result = 0;

cleanup:
	fpkt->type    = 2 ;
	fpkt->operate = FILE_RD_ACK ;
	fpkt->result  = result;
	fpkt->namelen = fnamelen;
	memcpy(fpkt->name,file,fnamelen);

	/* 尾部填充文件 ID 和文件大小，低位在前 */
	info = (unsigned char*)&fpkt->name[fnamelen] ;
	for (int i = 0 ; i < 4 ; i++) {
		*info++ = 0;
	}
	
	for (int i = 0 ; i < 4 ; i++) {
		*info++ = (fsize >> (8*i)) & 0x00ff;
	}

	iec104_protocol_send(bus,ASDU,info - ASDU,0);
	return ;
}




/**
  * @author   古么宁
  * @brief    从机接收到文件发送激活命令，调用 iec104slv_fsend_confirm 回复激活确认
  * @param    bus      : 总线
  * @param    ASDUrx   : 接收到的 ASDU 数据包
  * @param    ASDUlen  : 接收到的 ASDU 数据包长度
  * @return   don't care
*/
void iec104slv_fsend_req(iec10x_t * bus , uint8_t * ASDU,int32_t len)
{
	IEC_INDEX(bus->causelen,bus->cmnaddrlen);
	struct read_cmd_pkt *fpkt ;
	char * file = NULL;
	int    fnamelen = 0;

	/* 获取主站数据包内容 */
	fpkt = (struct read_cmd_pkt *)&ASDU[IEC_INFO + bus->infoaddrlen];

	if (fpkt->namelen == 0 || fpkt->namelen > 240) {
		goto cleanup ;
	}

	fnamelen = fpkt->namelen ;
	file = iec_malloc(fnamelen + 1) ;
	if (file == NULL) {
		goto cleanup ;
	}

	/* 提取主站命令读取的文件名为 file */
	memcpy(file,fpkt->name,fnamelen);
	file[fnamelen] = '\0';

cleanup:
	bus->fs.argv[0] = (size_t)file;
	bus->fs.argv[1] = (size_t)fnamelen;
	bus->fs.todo = iec104slv_fsend_confirm;
}






static void iec104slv_fconfirm(iec10x_t * bus)
{
	IEC_INDEX(bus->causelen,bus->cmnaddrlen);
	unsigned char * ASDU = ASDU_TXBUFF(bus);
	struct file_cmd *cmd = (struct file_cmd *)bus->fs.argv;
	struct file_confirm_pkt *fpkt;
	char * file     = cmd->file    ;
	int    fnamelen = cmd->namelen ;
	int    fsize    = cmd->fsize   ;
	int    fID      = cmd->fID     ;
	int    mode     = cmd->mode    ;
	unsigned char result  = 1;
	unsigned char operate = (mode == 'w') ? FILE_WR_ACK : FILE_RD_ACK;

	iec_file_t *iecfile = NULL;
	unsigned char * info = &ASDU[IEC_INFO + bus->infoaddrlen];

	/* 先填充基本信息 */
	ASDU[IEC_TYPE]          = 0xD2                    ;
	ASDU[IEC_QUALIFIER]     = 0x01                    ; 
	ASDU[IEC_COT]           = COT_ACTIVATE_CONFIRM    ;
	ASDU[IEC_COT+1]         = bus->connectaddr & 0xff ;
	ASDU[IEC_COMMON_ADDR]   = bus->cmnaddr     & 0xff ;
	ASDU[IEC_COMMON_ADDR+1] = bus->cmnaddr     >> 8   ;
	ASDU[IEC_INFO]          = 0                       ;
	ASDU[IEC_INFO+1]        = 0                       ;
	ASDU[IEC_INFO+2]        = 0                       ;

	/* 附加数据包 */
	fpkt = (struct file_confirm_pkt*)info;

	memset(&bus->fs,0,sizeof(struct iec_do));

	if (0 == fnamelen) {
		iec_free(file);
		goto cleanup ;
	}

	if ('r' == mode) {
		/* 读文件时，获取文件大小，并打开文件，记录文件参数至 argv 缓存区中 */
		fsize = iec_fsize(bus->workdir,file);
		if (fsize < 0) {
			fsize = 0 ;
			iec_free(file);
			goto cleanup;
		}

		iecfile = iec_fopen(bus->workdir,file,'r');
		if (NULL == iecfile) {
			fsize = 0 ;
			iec_free(file);
			goto cleanup;
		}
	}
	else {
		/* 写文件 */
		iecfile = iec_fopen(bus->workdir,file,'w');
		if (NULL == iecfile) {
			iec_free(file);
			goto cleanup;
		}
	}

	struct fsend_arg * tmp = (struct fsend_arg *)bus->fs.argv;
	tmp->file    = file    ;
	tmp->iecfile = iecfile ;
	tmp->fsize   = fsize   ;
	tmp->ftell   = 0       ;
	tmp->confirm = 0       ;

	/* 置位总线状态为文件传输忙碌 */
	bus->busy     |= IEC10x_FILE ;
	bus->interrupt = iec104slv_fclose;

	/* 如果为读命令，调用 iec104slv_fsend 发送文件
	   如果为写命令，iec104slv_frecv 等待接收 */
	bus->fs.todo   = (mode == 'w') ? NULL : iec104slv_fsend ;

	result = 0; // 回复激活成功

cleanup:
	fpkt->type    = 2        ;
	fpkt->operate = operate  ;
	fpkt->result  = result   ;
	fpkt->namelen = fnamelen ;
	memcpy(fpkt->name,file,fnamelen);

	/* 尾部填充文件 ID 和文件大小，低位在前 */
	info = (unsigned char*)&fpkt->name[fnamelen] ;
	for (int i = 0 ; i < 4 ; i++) {
		*info++ = (fID >> (8*i)) & 0x00ff;
	}
	
	for (int i = 0 ; i < 4 ; i++) {
		*info++ = (fsize >> (8*i)) & 0x00ff;
	}

	iec104_protocol_send(bus,ASDU,info - ASDU,0);
	return ;
}


/**
  * @author   古么宁
  * @brief    从机接收到文件激活命令
  * @param    bus      : 总线
  * @param    ASDUrx   : 接收到的 ASDU 数据包
  * @param    ASDUlen  : 接收到的 ASDU 数据包长度
  * @note     把相关内容保存至 bus->fs.argv 后 ，
  *           调用 iec104slv_fconfirm() 回复激活确认
  * @return   don't care
*/
void iec104slv_fopen(iec10x_t * bus , uint8_t * ASDU,int len , int mode)
{
	IEC_INDEX(bus->causelen,bus->cmnaddrlen);
	struct read_cmd_pkt *fpkt ;
	struct file_cmd     *cmd  ;
	char * file = NULL;
	int    fnamelen = 0;
	int    fsize    = 0;
	int    fID      = 0;

	/* 获取主站数据包内容 */
	fpkt = (struct read_cmd_pkt *)&ASDU[IEC_INFO + bus->infoaddrlen];
	cmd  = (struct file_cmd * )bus->fs.argv;

	if (fpkt->namelen == 0 || fpkt->namelen > 240) {
		goto cleanup ;
	}

	fnamelen = fpkt->namelen ;
	file = iec_malloc(fnamelen + 1) ;
	if (file == NULL) {
		goto cleanup ;
	}

	/* 提取主站命令读取的文件名为 file */
	memcpy(file,fpkt->name,fnamelen);
	file[fnamelen] = '\0';

	if (mode == 'w') {
		/* 写命令，提取文件 ID 和文件大小，读命令忽略 */
		unsigned char * info = (unsigned char *)&fpkt->name[fnamelen];
		for (int i = 0 ; i < 4 ; i++ , info++) {
			fID   |= ((*info) << (8 * i)) ;
		}
		for (int i = 0 ; i < 4 ; i++ , info++) {
			fsize |= ((*info) << (8 * i)) ;
		}
	}

cleanup:
	cmd->file    = file     ;
	cmd->namelen = fnamelen ;
	cmd->fsize   = fsize    ;
	cmd->fID     = fID      ;
	cmd->mode    = mode     ;
	bus->fs.todo = iec104slv_fconfirm;
}




/**
  * @author   古么宁
  * @brief    从机文件服务响应
  * @param    bus      : 总线
  * @param    ASDUrx   : 接收到的 ASDU 数据包
  * @param    ASDUlen  : 接收到的 ASDU 数据包长度
  * @return   don't care
*/
void iec104slv_file_service(iec10x_t * bus , uint8_t * ASDU,int32_t len)
{
	IEC_INDEX(bus->causelen,bus->cmnaddrlen);
	uint8_t * info = &ASDU[IEC_INFO + bus->infoaddrlen];
	if (info[0] != 2) {
		return ;
	}

	if (info[1] == DIR_READ) {
		/* 读目录命令，主站获取从站的目录列表 */
		iec104slv_dir_open(bus,ASDU,len);
	}
	else 
	if (info[1] == FILE_RD_CMD) {
		/* 读文件激活命令 */
		iec104slv_fopen(bus,ASDU,len,'r');
	}
	else
	if (info[1] == FILE_RD_DATA_ACK) {
		/* 读文件传输确认，主站收到了上一包数据帧并返回确认 */
		iec104slv_fsend_ack(bus,ASDU,len);
	}
	else
	if (info[1] == FILE_WR_CMD) {
		/* 写文件激活命令 */
		iec104slv_fopen(bus,ASDU,len,'w');
	}
	else
	if (info[1] == FILE_WR_DATA) {
		/* 写文件数据传输 */
		fiec104slv_fwrite(bus,ASDU,len);
	}
	else {
		iec10x_warn(bus,"Unknown file operation type[0x%x].\r\n",info[1]);
	}
}


#endif /* #if (ENABLE_FILE_TRANS) */
